import os
import urllib
import webapp2

from google.appengine.api        import mail
from google.appengine.ext        import db
from google.appengine.ext.webapp import template

from model import user



class RecoveryPage(webapp2.RequestHandler):
    """
    Page with a form to give the email address.
    
    When rendernig a page, provides template dict errors with following kinds
    of errors (keys):
    - email_not_defined
    - email_invalid_length
    - email_invalid_format
    - user_not_found
    
    """
    
    ### Constants ###
    REQUEST_URL_AGUMENT = "request"
    HEADER              = "Password Recovery"
    APP_EMAIL_ADDRESS   = "admin@zppchat.appspotmail.com"
    RECOVERY_EMAIL_BODY = \
"""Hi,
        
You have requested password recovery on an account (username: %s) at ZPPChat application. To proceed just visit the link below.
%s
        
Do not reply to this message."""
    
    def get(self):
        """Page with an empty form."""
        self.render_page()
    
    def post(self):
        """Recovery request."""
        
        email = self.request.get("email")
        values = { "email": email }
        
        try:
            if email:
                user.User.validate_email(email)
                
                user_obj = user.User.all().filter("email", email).get()
                if user_obj:
                    request_key = self.generate_key(user_obj)
                    self.send_email(user_obj.email, user_obj.username, request_key)
                    redirect_url = webapp2.uri_for("password_recovery_sent") + \
                                   "?" + urllib.urlencode({ "email": email })
                    self.redirect(redirect_url)
                else:
                    self.render_page(values, { "user_not_found": True })
            else:
                self.render_page(values, { "email_not_defined": True })
                return
        except user.EmailInvalidLength:
            self.render_page(values, { "email_invalid_length": True })
            return
        except user.EmailInvalidFormat:
            self.render_page(values, { "email_invalid_format": True })
            return
        except db.TransactionFailedError:
            self.redirect(webapp2.uri_for("password_recovery_transaction_fail"))
            return 
            
    
    def render_page(self, values={}, errors={}):
        """Page with a form filled with given values and error shown."""
        template_path = os.path.join(
            os.path.dirname(__file__),
            "../templates/password-recovery.html"
        )
        template_values = {
            "form_action_url": webapp2.uri_for("password_recovery"),
            "main_page_url"  : webapp2.uri_for("main"),
            "values"         : values,
            "errors"         : errors,
            "limits"         : user.LIMITS,
            "error_messages" : user.ERROR_MESSAGES
        }
        self.response.out.write(template.render(template_path, template_values))
    
    def generate_key(self, user_obj):
        """
        Generates key used to reset the password. Creates and stores
        corresponding PasswordRecoveryR Throws TransactionFailedError.
        
        """
        # Delete all the previous requests for that user.
        for old_request in user_obj.passwordrecoveryrequest_set.run():
            old_request.delete()
        request = user.PasswordRecoveryRequest(user=user_obj)
        request.put()
        return request.key()
    
    def send_email(self, to_address, username, request_key):
        """Sends email with password recovery informaton."""
        
        # Link that is send to the user via email. When user browses it he's
        # able to enter a new password.
        link = self.request.host_url + webapp2.uri_for("password_recovery_new") + "?" + \
               urllib.urlencode({ RecoveryPage.REQUEST_URL_AGUMENT: request_key })
        
        mail.send_mail(
            sender  = "ZPPChat Administration <%s>" % self.APP_EMAIL_ADDRESS,
            to      = to_address,
            subject = "ZPPChat Password recovery",
            body    = self.RECOVERY_EMAIL_BODY % (username, link)
        )
        

class PasswordSentPage(webapp2.RequestHandler):
    """Page with a message that an email with instructions was sent."""
    
    def get(self):
        template_path = os.path.join(
            os.path.dirname(__file__),
            "../templates/start-screen-message.html"
        )
        template_values = {
            "header"         : RecoveryPage.HEADER,
            "message"        : "We've sent an email to <b>%s</b> with instructions on what to do next." %
                                   self.request.get("email"),
            "navigate_to_url": webapp2.uri_for("main")
        }
        self.response.out.write(template.render(template_path, template_values))


class NewPasswordPage(webapp2.RequestHandler):
    """
    Page with a form to enter new password.
    
    When rendernig a page, provides template dict errors with following kinds
    of errors (keys):
    - password_invalid_format
    - password_invalid_length
    - password_not_defined
    
    """
    
    def get(self):
        """Page with an empty form."""
        request_key = self.request.get(RecoveryPage.REQUEST_URL_AGUMENT)
        try: 
            request = user.PasswordRecoveryRequest.get(request_key)
            values  = { "username": request.user.username }
            
            self.render_page(self.request.query_string, values)
        except (db.BadKeyError, db.KindError, AttributeError):
            self.redirect(webapp2.uri_for("password_recovery_request_not_found"))
        except db.ReferencePropertyResolveError:
            self.redirect(webapp2.uri_for("password_recovery_user_not_found"))
            return
    
    def post(self):
        """Password change request."""
        request_key  = self.request.get(RecoveryPage.REQUEST_URL_AGUMENT)
        new_password = self.request.get("password")
        
        query_str = self.request.query_string
        values = { "password": new_password }
        
        try:
            if new_password:
                # Check if the request is valid (= exists).
                request  = user.PasswordRecoveryRequest.get(request_key)
                user_obj = request.user
                
                # New password's validation.
                user.User.validate_password(new_password)
                
                request.delete()
                
                # Setting new password to user.
                user_obj.password = new_password
                user_obj.put()
                self.redirect(webapp2.uri_for("password_recovery_success"))
            else:
                self.render_page(query_str, values, { "password_not_defined": True })
                return
        except (db.BadKeyError, db.KindError, db.NotSavedError):
            self.redirect(webapp2.uri_for("password_recovery_request_not_found"))
            return
        except db.ReferencePropertyResolveError:
            self.redirect(webapp2.uri_for("password_recovery_user_not_found"))
            return
        except user.PasswordInvalidFormat:
            self.render_page(query_str, values, { "password_invalid_format": True })
            return
        except user.PasswordInvalidLength:
            self.render_page(query_str, values, { "password_invalid_length": True })
            return
        except db.TransactionFailedError:
            self.redirect(webapp2.uri_for("password_recovery_transaction_fail"))
            return
    
    def render_page(self, query_str="", values={}, errors={}):
        """Page with form filled with given values."""
        template_path = os.path.join(
            os.path.dirname(__file__),
            "../templates/password-recovery-new.html"
        )
        template_values = {
            "form_action_url": webapp2.uri_for("password_recovery_new") + "?" + query_str,
            "limits"         : user.LIMITS,
            "error_messages" : user.ERROR_MESSAGES,
            "values"         : values,
            "errors"         : errors
        }
        self.response.out.write(template.render(template_path, template_values))


class SuccessPage(webapp2.RequestHandler):
    """Page shown after a successful password change."""
    
    def get(self):
        template_values = {
            "header"         : RecoveryPage.HEADER,
            "message"        : "You have successfully changed your password.",
            "navigate_to_url": webapp2.uri_for("main")
        }
        template_path = os.path.join(
            os.path.dirname(__file__),
            '../templates/start-screen-message.html'
        )        
        self.response.out.write(template.render(template_path, template_values))


class TransactionFailPage(webapp2.RequestHandler):
    """Page shown on a TransactionFailedError on storing new password."""
    
    def get(self):
        template_path = os.path.join(
            os.path.dirname(__file__),
            '../templates/start-screen-message.html'
        )
        template_values = {
            "header"         : RecoveryPage.HEADER,
            "message"        : "Unfortunately the new password could not be changed due to database problems. Try again in a few minutes.",
            "navigate_to_url": webapp2.uri_for("password_recovery"),
            "error"          : True
        }
        self.response.out.write(template.render(template_path, template_values))


class UserNotFoundPage(webapp2.RequestHandler):
    """Page shown when request carries no/inexisting user."""
    
    def get(self):
        template_path = os.path.join(
            os.path.dirname(__file__),
            '../templates/start-screen-message.html'
        )
        template_values = {
            "header"         : RecoveryPage.HEADER,
            "message"        : "User from this request not found.",
            "navigate_to_url": webapp2.uri_for("main"),
            "error"          : True
        }
        self.response.out.write(template.render(template_path, template_values))


class RequestNotFoundPage(webapp2.RequestHandler):
    """Page shown when request's key is invalid."""
    
    def get(self):
        template_path = os.path.join(
            os.path.dirname(__file__),
            '../templates/start-screen-message.html'
        )
        template_values = {
            "header"         : RecoveryPage.HEADER,
            "message"        : "Request is invalid.",
            "navigate_to_url": webapp2.uri_for("main"),
            "error"          : True
        }
        self.response.out.write(template.render(template_path, template_values))